/*
  zm.c - zmodem protocol handling lowlevelstuff
  Copyright (C) until 1998 Chuck Forsberg (OMEN Technology Inc)
  Copyright (C) 1996, 1997 Uwe Ohse

  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2, or (at your option)
  any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
  02111-1307, USA.

  originally written by Chuck Forsberg
*/
/* historical comment: -- uwe
 *   Z M . C
 *    ZMODEM protocol primitives
 *    05-09-88  Chuck Forsberg Omen Technology Inc
 *
 * Entry point Functions:
 *	zsbhdr(type, hdr) send binary header
 *	zshhdr(type, hdr) send hex header
 *	zgethdr(hdr, eflag) receive header - binary or hex
 *	zsdata(buf, len, frameend) send data
 *	zrdata(buf, len, bytes_received) receive data
 *	stohdr(pos) store position data in Txhdr
 *	long rclhdr(hdr) recover position offset from header
 */


#include "zglobal.h"

#include <stdio.h>

unsigned int Rxtimeout = 100;        /* Tenths of seconds to wait for something */

/* Globals used by ZMODEM functions */
int Rxframeind;        /* ZBIN ZBIN32, or ZHEX type of frame received */
int Rxtype;        /* Type of header received */
char Rxhdr[4];        /* Received header */
char Txhdr[4];        /* Transmitted header */
long Txpos;        /* Transmitted file position */
int Txfcs32;        /* TRUE means send binary frames with 32 bit FCS */
int Crc32t;        /* Display flag indicating 32 bit CRC being sent */
int Crc32;        /* Display flag indicating 32 bit CRC being received */
int Znulls;        /* Number of nulls to send at beginning of ZDATA hdr */
char Attn[ZATTNLEN + 1];    /* Attention string rx sends to tx on err */

static char lastsent;    /* Last char we sent */
int turbo_escape;
int bytes_per_error = 0;

static const char *frametypes[] = {
        "Carrier Lost",        /* -3 */
        "TIMEOUT",        /* -2 */
        "ERROR",        /* -1 */
#define FTOFFSET 3
        "ZRQINIT",
        "ZRINIT",
        "ZSINIT",
        "ZACK",
        "ZFILE",
        "ZSKIP",
        "ZNAK",
        "ZABORT",
        "ZFIN",
        "ZRPOS",
        "ZDATA",
        "ZEOF",
        "ZFERR",
        "ZCRC",
        "ZCHALLENGE",
        "ZCOMPL",
        "ZCAN",
        "ZFREECNT",
        "ZCOMMAND",
        "ZSTDERR",
        "xxxxx"
#define FRTYPES 22    /* Total number of frame types in this array */
        /*  not including psuedo negative entries */
};

#define badcrc _("Bad CRC")

/* static char *badcrc = "Bad CRC"; */
static inline int noxrd7(void);

static inline int zdlread(void);

static int zdlread2(int);

static inline int zgeth1(void);

static void zputhex(int c, char *pos);

static inline int zgethex(void);

static int zrbhdr(char *hdr);

static int zrbhdr32(char *hdr);

static int zrhhdr(char *hdr);

static char zsendline_tab[256];

static int zrdat32(char *buf, int length, size_t *);

static void zsbh32(char *hdr, int type);

extern int zmodem_requested;

#define sendline(c) putchar((c) & 0377)
#define xsendline(c) putchar(c)

/*
 * Read a character from the modem line with timeout.
 *  Eat parity, XON and XOFF characters.
 */
static inline int
noxrd7(void) {
    register int c;

    for (;;) {
        if ((c = READLINE_PF(Rxtimeout)) < 0)
            return c;
        switch (c &= 0177) {
            case XON:
            case XOFF:
                continue;
            default:
                if (Zctlesc && !(c & 0140))
                    continue;
            case '\r':
            case '\n':
            case ZDLE:
                return c;
        }
    }
}

static inline int
zgeth1(void) {
    register int c, n;

    if ((c = noxrd7()) < 0)
        return c;
    n = c - '0';
    if (n > 9)
        n -= ('a' - ':');
    if (n & ~0xF)
        return MYERROR;
    if ((c = noxrd7()) < 0)
        return c;
    c -= '0';
    if (c > 9)
        c -= ('a' - ':');
    if (c & ~0xF)
        return MYERROR;
    c += (n << 4);
    return c;
}

/* Decode two lower case hex digits into an 8 bit byte value */
static inline int
zgethex(void) {
    register int c;

    c = zgeth1();
    VPRINTF(9, "zgethex: %02X", c);
    return c;
}

/*
 * Read a byte, checking for ZMODEM escape encoding
 *  including CAN*5 which represents a quick abort
 */
static inline int
zdlread(void) {
    int c;
    /* Quick check for non control characters */
    if ((c = READLINE_PF(Rxtimeout)) & 0140)
        return c;
    return zdlread2(c);
}

/* no, i don't like gotos. -- uwe */
static int
zdlread2(int c) {
    goto jump_over; /* bad style */

    again:
    /* Quick check for non control characters */
    if ((c = READLINE_PF(Rxtimeout)) & 0140)
        return c;
    jump_over:
    switch (c) {
        case ZDLE:
            break;
        case XON:
        case (XON | 0200):
        case XOFF:
        case (XOFF | 0200):
            goto again;
        default:
            if (Zctlesc && !(c & 0140)) {
                goto again;
            }
            return c;
    }
    again2:
    if ((c = READLINE_PF(Rxtimeout)) < 0)
        return c;
    if (c == CAN && (c = READLINE_PF(Rxtimeout)) < 0)
        return c;
    if (c == CAN && (c = READLINE_PF(Rxtimeout)) < 0)
        return c;
    if (c == CAN && (c = READLINE_PF(Rxtimeout)) < 0)
        return c;
    switch (c) {
        case CAN:
            return GOTCAN;
        case ZCRCE:
        case ZCRCG:
        case ZCRCQ:
        case ZCRCW:
            return (c | GOTOR);
        case ZRUB0:
            return 0177;
        case ZRUB1:
            return 0377;
        case XON:
        case (XON | 0200):
        case XOFF:
        case (XOFF | 0200):
            goto again2;
        default:
            if (Zctlesc && !(c & 0140)) {
                goto again2;
            }
            if ((c & 0140) == 0100)
                return (c ^ 0100);
            break;
    }
    VPRINTF(2, _("Bad escape sequence %x"), c);
    return MYERROR;
}


/*
 * Send character c with ZMODEM escape sequence encoding.
 *  Escape XON, XOFF. Escape CR following @ (Telenet net escape)
 */
inline void
zsendline(int c) {

    switch (zsendline_tab[(unsigned) (c &= 0377)]) {
        case 0:
            xsendline(lastsent = c);
            break;
        case 1:
            xsendline(ZDLE);
            c ^= 0100;
            xsendline(lastsent = c);
            break;
        case 2:
            if ((lastsent & 0177) != '@') {
                xsendline(lastsent = c);
            } else {
                xsendline(ZDLE);
                c ^= 0100;
                xsendline(lastsent = c);
            }
            break;
    }
}

static inline void
zsendline_s(const char *s, size_t count) {
    const char *end = s + count;
    while (s != end) {
        int last_esc = 0;
        const char *t = s;
        while (t != end) {
            last_esc = zsendline_tab[(unsigned) ((*t) & 0377)];
            if (last_esc)
                break;
            t++;
        }
        if (t != s) {
            fwrite(s, (size_t) (t - s), 1, stdout);
            lastsent = t[-1];
            s = t;
        }
        if (last_esc) {
            int c = *s;
            switch (last_esc) {
                case 0:
                    xsendline(lastsent = c);
                    break;
                case 1:
                    xsendline(ZDLE);
                    c ^= 0100;
                    xsendline(lastsent = c);
                    break;
                case 2:
                    if ((lastsent & 0177) != '@') {
                        xsendline(lastsent = c);
                    } else {
                        xsendline(ZDLE);
                        c ^= 0100;
                        xsendline(lastsent = c);
                    }
                    break;
            }
            s++;
        }
    }
}


/* Send ZMODEM binary header hdr of type type */
void
zsbhdr(int type, char *hdr) {
    register int n;
    register unsigned short crc;

    VPRINTF(3, "zsbhdr: %s %lx", frametypes[type + FTOFFSET], rclhdr(hdr));
    if (type == ZDATA)
        for (n = Znulls; --n >= 0;)
            xsendline(0);

    xsendline(ZPAD);
    xsendline(ZDLE);

    Crc32t = Txfcs32;
    if (Crc32t)
        zsbh32(hdr, type);
    else {
        xsendline(ZBIN);
        zsendline(type);
        crc = updcrc(type, 0);

        for (n = 4; --n >= 0; ++hdr) {
            zsendline(*hdr);
            crc = updcrc((0377 & *hdr), crc);
        }
        crc = updcrc(0, updcrc(0, crc));
        zsendline(crc >> 8);
        zsendline(crc);
    }
    if (type != ZDATA)
        flushmo();
}


/* Send ZMODEM binary header hdr of type type */
static void
zsbh32(char *hdr, int type) {
    register int n;
    register unsigned long crc;

    xsendline(ZBIN32);
    zsendline(type);
    crc = 0xFFFFFFFFL;
    crc = UPDC32(type, crc);

    for (n = 4; --n >= 0; ++hdr) {
        crc = UPDC32((0377 & *hdr), crc);
        zsendline(*hdr);
    }
    crc = ~crc;
    for (n = 4; --n >= 0;) {
        zsendline((int) crc);
        crc >>= 8;
    }
}

/* Send ZMODEM HEX header hdr of type type */
void
zshhdr(int type, char *hdr) {
    register int n;
    register unsigned short crc;
    char s[30];
    size_t len;

    VPRINTF(3, "zshhdr: %s %lx", frametypes[(type & 0x7f) + FTOFFSET], rclhdr(hdr));
    s[0] = ZPAD;
    s[1] = ZPAD;
    s[2] = ZDLE;
    s[3] = ZHEX;
    zputhex(type & 0x7f, s + 4);
    len = 6;
    Crc32t = 0;

    crc = updcrc((type & 0x7f), 0);
    for (n = 4; --n >= 0; ++hdr) {
        zputhex(*hdr, s + len);
        len += 2;
        crc = updcrc((0377 & *hdr), crc);
    }
    crc = updcrc(0, updcrc(0, crc));
    zputhex(crc >> 8, s + len);
    zputhex(crc, s + len + 2);
    len += 4;

    /* Make it printable on remote machine */
    s[len++] = 015;
    s[len++] = 0212;
    /*
     * Uncork the remote in case a fake XOFF has stopped data flow
     */
    if (type != ZFIN && type != ZACK) {
        s[len++] = 021;
    }
    flushmo();
    int nw = write(1, s, len);
}

/*
 * Send binary array buf of length length, with ending ZDLE sequence frameend
 */
static const char *Zendnames[] = {"ZCRCE", "ZCRCG", "ZCRCQ", "ZCRCW"};

void
zsdata(const char *buf, size_t length, int frameend) {
    register unsigned short crc;

    VPRINTF(3, "zsdata: %lu %s", (unsigned long) length, Zendnames[(frameend - ZCRCE) & 3]);
    crc = 0;
    do {
        zsendline(*buf);
        crc = updcrc((0377 & *buf), crc);
        buf++;
    } while (--length > 0);
    xsendline(ZDLE);
    xsendline(frameend);
    crc = updcrc(frameend, crc);

    crc = updcrc(0, updcrc(0, crc));
    zsendline(crc >> 8);
    zsendline(crc);
    if (frameend == ZCRCW) {
        xsendline(XON);
        flushmo();
    }
}

void
zsda32(const char *buf, size_t length, int frameend) {
    int c;
    unsigned long crc;
    int i;
    VPRINTF(3, "zsdat32: %d %s", length, Zendnames[(frameend - ZCRCE) & 3]);

    crc = 0xFFFFFFFFL;
    zsendline_s(buf, length);
    for (; length; length--) {
        c = *buf & 0377;
        crc = UPDC32(c, crc);
        buf++;
    }
    xsendline(ZDLE);
    xsendline(frameend);
    crc = UPDC32(frameend, crc);

    crc = ~crc;
    for (i = 4; --i >= 0;) {
        c = (int) crc;
        if (c & 0140)
            xsendline(lastsent = c);
        else
            zsendline(c);
        crc >>= 8;
    }
    if (frameend == ZCRCW) {
        xsendline(XON);
        flushmo();
    }
}

#define COUNT_BLK(x)

/*
 * Receive array buf of max length with ending ZDLE sequence
 *  and CRC.  Returns the ending character or error code.
 *  NB: On errors may store length+1 bytes!
 */
int
zrdata(char *buf, int length, size_t *bytes_received) {
    register int c;
    register unsigned short crc;
    register char *end;
    register int d;

    *bytes_received = 0;
    if (Rxframeind == ZBIN32)
        return zrdat32(buf, length, bytes_received);

    crc = 0;
    end = buf + length;
    while (buf <= end) {
        if ((c = zdlread()) & ~0377) {
            crcfoo:
            switch (c) {
                case GOTCRCE:
                case GOTCRCG:
                case GOTCRCQ:
                case GOTCRCW: {
                    d = c;
                    c &= 0377;
                    crc = updcrc(c, crc);
                    if ((c = zdlread()) & ~0377)
                        goto crcfoo;
                    crc = updcrc(c, crc);
                    if ((c = zdlread()) & ~0377)
                        goto crcfoo;
                    crc = updcrc(c, crc);
                    if (crc & 0xFFFF) {
                        zperr(badcrc);
                        return MYERROR;
                    }
                    *bytes_received = length - (end - buf);
                    COUNT_BLK(*bytes_received);
                    VPRINTF(3, "zrdata: %lu  %s", (unsigned long) (*bytes_received), Zendnames[(d - GOTCRCE) & 3]);
                    return d;
                }
                case GOTCAN:
                    zperr(_("Sender Canceled"));
                    return ZCAN;
                case TIMEOUT:
                    zperr(_("TIMEOUT"));
                    return c;
                default:
                    zperr(_("Bad data subpacket"));
                    return c;
            }
        }
        *buf++ = c;
        crc = updcrc(c, crc);
    }
    zperr(_("Data subpacket too long"));
    return MYERROR;
}

static int
zrdat32(char *buf, int length, size_t *bytes_received) {
    register int c;
    register unsigned long crc;
    register char *end;
    register int d;

    crc = 0xFFFFFFFFL;
    end = buf + length;
    while (buf <= end) {
        if ((c = zdlread()) & ~0377) {
            crcfoo:
            switch (c) {
                case GOTCRCE:
                case GOTCRCG:
                case GOTCRCQ:
                case GOTCRCW:
                    d = c;
                    c &= 0377;
                    crc = UPDC32(c, crc);
                    if ((c = zdlread()) & ~0377)
                        goto crcfoo;
                    crc = UPDC32(c, crc);
                    if ((c = zdlread()) & ~0377)
                        goto crcfoo;
                    crc = UPDC32(c, crc);
                    if ((c = zdlread()) & ~0377)
                        goto crcfoo;
                    crc = UPDC32(c, crc);
                    if ((c = zdlread()) & ~0377)
                        goto crcfoo;
                    crc = UPDC32(c, crc);
                    if (crc != 0xDEBB20E3) {
                        zperr(badcrc);
                        return MYERROR;
                    }
                    *bytes_received = length - (end - buf);
                    COUNT_BLK(*bytes_received);
                    VPRINTF(3, "zrdat32: %lu %s", (unsigned long) *bytes_received, Zendnames[(d - GOTCRCE) & 3]);
                    return d;
                case GOTCAN:
                    zperr(_("Sender Canceled"));
                    return ZCAN;
                case TIMEOUT:
                    zperr(_("TIMEOUT"));
                    return c;
                default:
                    zperr(_("Bad data subpacket"));
                    return c;
            }
        }
        *buf++ = c;
        crc = UPDC32(c, crc);
    }
    zperr(_("Data subpacket too long"));
    return MYERROR;
}

/*
 * Read a ZMODEM header to hdr, either binary or hex.
 *  eflag controls local display of non zmodem characters:
 *	0:  no display
 *	1:  display printing characters only
 *	2:  display all non ZMODEM characters
 *  On success, set Zmodem to 1, set Rxpos and return type of header.
 *   Otherwise return negative on error.
 *   Return ERROR instantly if ZCRCW sequence, for fast error recovery.
 */
int
zgethdr(char *hdr, int eflag, size_t *Rxpos) {
    register int c, cancount;
    unsigned int max_garbage; /* Max bytes before start of frame */
    size_t rxpos = 0; /* keep gcc happy */

    max_garbage = Zrwindow + Baudrate;
    Rxframeind = Rxtype = 0;

    startover:
    cancount = 5;
    again:
    /* Return immediate ERROR if ZCRCW sequence seen */
    switch (c = READLINE_PF(Rxtimeout)) {
        case RCDO:
        case TIMEOUT:
            goto fifi;
        case CAN:
        gotcan:
            if (--cancount <= 0) {
                c = ZCAN;
                goto fifi;
            }
            switch (c = READLINE_PF(1)) {
                case TIMEOUT:
                    goto again;
                case ZCRCW:
                    c = MYERROR;
                    /* **** FALL THRU TO **** */
                case RCDO:
                    goto fifi;
                default:
                    break;
                case CAN:
                    if (--cancount <= 0) {
                        c = ZCAN;
                        goto fifi;
                    }
                    goto again;
            }
            /* **** FALL THRU TO **** */
        default:
        agn2:
            if (--max_garbage == 0) {
                zperr(_("Garbage count exceeded"));
                return (MYERROR);
            }
            if (eflag && ((c &= 0177) & 0140) && Verbose)
                vchar(c);
            else if (eflag > 1 && Verbose)
                vchar(c);
            goto startover;
        case ZPAD | 0200:        /* This is what we want. */
        case ZPAD:        /* This is what we want. */
            break;
    }
    cancount = 5;
    splat:
    switch (c = noxrd7()) {
        case ZPAD:
            goto splat;
        case RCDO:
        case TIMEOUT:
            goto fifi;
        default:
            goto agn2;
        case ZDLE:        /* This is what we want. */
            break;
    }

    switch (c = noxrd7()) {
        case RCDO:
        case TIMEOUT:
            goto fifi;
        case ZBIN:
            Rxframeind = ZBIN;
            Crc32 = FALSE;
            c = zrbhdr(hdr);
            break;
        case ZBIN32:
            Crc32 = Rxframeind = ZBIN32;
            c = zrbhdr32(hdr);
            break;
        case ZHEX:
            Rxframeind = ZHEX;
            Crc32 = FALSE;
            c = zrhhdr(hdr);
            break;
        case CAN:
            goto gotcan;
        default:
            goto agn2;
    }
    rxpos = hdr[ZP3] & 0377;
    rxpos = (rxpos << 8) + (hdr[ZP2] & 0377);
    rxpos = (rxpos << 8) + (hdr[ZP1] & 0377);
    rxpos = (rxpos << 8) + (hdr[ZP0] & 0377);
 fifi:
    switch (c) {
        case GOTCAN:
            c = ZCAN;
            /* **** FALL THRU TO **** */
        case ZNAK:
        case ZCAN:
        case MYERROR:
        case TIMEOUT:
        case RCDO:
            zperr(_("Got %s"), frametypes[c + FTOFFSET]);
            /* **** FALL THRU TO **** */
        default:
            if (c >= -3 && c <= FRTYPES)
                VPRINTF(3, "zgethdr: %s %lx", frametypes[c + FTOFFSET], (unsigned long) rxpos);
            else
                VPRINTF(3, "zgethdr: %d %lx", c, (unsigned long) rxpos);
    }
    if (Rxpos)
        *Rxpos = rxpos;
    return c;
}

/* Receive a binary style header (type and position) */
static int
zrbhdr(char *hdr) {
    register int c, n;
    register unsigned short crc;

    if ((c = zdlread()) & ~0377)
        return c;
    Rxtype = c;
    crc = updcrc(c, 0);

    for (n = 4; --n >= 0; ++hdr) {
        if ((c = zdlread()) & ~0377)
            return c;
        crc = updcrc(c, crc);
        *hdr = c;
    }
    if ((c = zdlread()) & ~0377)
        return c;
    crc = updcrc(c, crc);
    if ((c = zdlread()) & ~0377)
        return c;
    crc = updcrc(c, crc);
    if (crc & 0xFFFF) {
        zperr(badcrc);
        return MYERROR;
    }
    protocol = ZM_ZMODEM;
    zmodem_requested = TRUE;
    return Rxtype;
}

/* Receive a binary style header (type and position) with 32 bit FCS */
static int
zrbhdr32(char *hdr) {
    register int c, n;
    register unsigned long crc;

    if ((c = zdlread()) & ~0377)
        return c;
    Rxtype = c;
    crc = 0xFFFFFFFFL;
    crc = UPDC32(c, crc);
#ifdef DEBUGZ
	VPRINTF(3,"zrbhdr32 c=%X  crc=%lX", c, crc);
#endif

    for (n = 4; --n >= 0; ++hdr) {
        if ((c = zdlread()) & ~0377)
            return c;
        crc = UPDC32(c, crc);
        *hdr = c;
#ifdef DEBUGZ
		VPRINTF(3, "zrbhdr32 c=%X  crc=%lX", c, crc);
#endif
    }
    for (n = 4; --n >= 0;) {
        if ((c = zdlread()) & ~0377)
            return c;
        crc = UPDC32(c, crc);
#ifdef DEBUGZ
		VPRINTF(3, "zrbhdr32 c=%X  crc=%lX", c, crc);
#endif
    }
    if (crc != 0xDEBB20E3) {
        zperr(badcrc);
        return MYERROR;
    }
    protocol = ZM_ZMODEM;
    zmodem_requested = TRUE;
    return Rxtype;
}


/* Receive a hex style header (type and position) */
static int
zrhhdr(char *hdr) {
    register int c;
    register unsigned short crc;
    register int n;

    if ((c = zgethex()) < 0)
        return c;
    Rxtype = c;
    crc = updcrc(c, 0);

    for (n = 4; --n >= 0; ++hdr) {
        if ((c = zgethex()) < 0)
            return c;
        crc = updcrc(c, crc);
        *hdr = c;
    }
    if ((c = zgethex()) < 0)
        return c;
    crc = updcrc(c, crc);
    if ((c = zgethex()) < 0)
        return c;
    crc = updcrc(c, crc);
    if (crc & 0xFFFF) {
        zperr(badcrc);
        return MYERROR;
    }
    switch (c = READLINE_PF(1)) {
        case 0215:
            /* **** FALL THRU TO **** */
        case 015:
            /* Throw away possible cr/lf */
            READLINE_PF(1);
            break;
    }
    protocol = ZM_ZMODEM;
    zmodem_requested = TRUE;
    return Rxtype;
}

/* Write a byte as two hex digits */
static void
zputhex(int c, char *pos) {
    static char digits[] = "0123456789abcdef";

    VPRINTF(9, "zputhex: %02X", c);
    pos[0] = digits[(c & 0xF0) >> 4];
    pos[1] = digits[c & 0x0F];
}

void
zsendline_init(void) {
    int i;
    for (i = 0; i < 256; i++) {
        if (i & 0140) {
            zsendline_tab[i] = 0;
        } else {
            switch (i) {
                case ZDLE:
                case XOFF: /* ^Q */
                case XON: /* ^S */
                case (XOFF | 0200):
                case (XON | 0200):
                    zsendline_tab[i] = 1;
                    break;
                case 020: /* ^P */
                case 0220:
                    if (turbo_escape) {
                        zsendline_tab[i] = 0;
                    } else {
                        zsendline_tab[i] = 1;
                    }
                    break;
                case 015:
                case 0215:
                    if (Zctlesc) {
                        zsendline_tab[i] = 1;
                    } else if (!turbo_escape) {
                        zsendline_tab[i] = 2;
                    } else {
                        zsendline_tab[i] = 0;
                    }
                    break;
                default:
                    if (Zctlesc) {
                        zsendline_tab[i] = 1;
                    } else {
                        zsendline_tab[i] = 0;
                    }
            }
        }
    }
}


/* Store pos in Txhdr */
void
stohdr(size_t pos) {
    long lpos = (long) pos;
    Txhdr[ZP0] = lpos;
    Txhdr[ZP1] = lpos >> 8;
    Txhdr[ZP2] = lpos >> 16;
    Txhdr[ZP3] = lpos >> 24;
}

/* Recover a long integer from a header */
long
rclhdr(char *hdr) {
    long l;

    l = (hdr[ZP3] & 0377);
    l = (l << 8) | (hdr[ZP2] & 0377);
    l = (l << 8) | (hdr[ZP1] & 0377);
    l = (l << 8) | (hdr[ZP0] & 0377);
    return l;
}

/* End of zm.c */
